namespace ts {    
    export type ErrorCallback = (message: DiagnosticMessage, length: number) => void;
    
    const 关键字对象: MapLike<关键字> = {
        assert: 语法.Assert,
        bit: 语法.Bit,
        bits: 语法.Bits,
        class: 语法.Class,
        code: 语法.Code,
        dag: 语法.Dag,
        def: 语法.Def,
        defm: 语法.Defm,
        defset: 语法.Defset,
        defvar: 语法.Defvar,
        else: 语法.ElseKW,
        false: 语法.FalseKW,
        field: 语法.Field,
        foreach: 语法.Foreach,
        if: 语法.If,
        in: 语法.In,
        include: 语法.Include,
        int: 语法.Int,
        let: 语法.Let,
        list: 语法.List,
        multiclass: 语法.MultiClass,
        string: 语法.String,
        then: 语法.Then,
        true: 语法.TrueKW
    }

    const 取反语法对象: MapLike<关键字> = {
        "!eq": 语法.XEq,
        "!ne": 语法.XNe,
        "!le": 语法.XLe,
        "!lt": 语法.XLt,
        "!ge": 语法.XGe,
        "!gt": 语法.XGt,
        "!if": 语法.XIf,
        "!cond": 语法.XCond,
        "!isa": 语法.XIsA,
        "!head": 语法.XHead,
        "!tail": 语法.XTail,
        "!size": 语法.XSize,
        "!con": 语法.XConcat,
        "!dag": 语法.XDag,
        "!add": 语法.XADD,
        "!sub": 语法.XSUB,
        "!mul": 语法.XMUL,
        "!not": 语法.XNOT,
        "!and": 语法.XAND,
        "!or": 语法.XOR,
        "!xor": 语法.XXOR,
        "!shl": 语法.XSHL,
        "!sra": 语法.XSRA,
        "!srl": 语法.XSRL,
        "!cast": 语法.XCast,
        "!empty": 语法.XEmpty,
        "!subst": 语法.XSubst,
        "!foldl": 语法.XFoldl,
        "!foreach": 语法.XForEach,
        "!filter": 语法.XFilter,
        "!listconcat": 语法.XListConcat,
        "!listsplat": 语法.XListSplat,
        "!strconcat": 语法.XStrConcat,
        "!interleave": 语法.XInterleave,
        "!substr": 语法.XSubstr,
        "!find": 语法.XFind,
        "!setdagop": 语法.XSetDagOp,
        "!setop": 语法.XSetDagOp,
        "!getdagop": 语法.XGetDagOp,
        "!getop": 语法.XGetDagOp
    }

    const 文本转取反关键字 = new Map(getEntries(取反语法对象));

    const textToKeyword = new Map(getEntries(关键字对象));

    const PreprocessorDirs: MapLike<关键字> = {
        ifdef: 语法.Ifdef,
        ifndef: 语法.Ifndef,
        else: 语法.Else,
        endif: 语法.Endif,
        definr: 语法.Define,
    }

    function isUnicodeIdentifierPart(code: number) {
        return !isWhiteSpaceLike(code) && !部分标点符号(code);
    }

    const 预处理文本关键字 = new Map(getEntries(PreprocessorDirs));


    /* @internal */
    export function tokenIsIdentifierOrKeyword(token: 语法): boolean {
        return token >= 语法.id;
    }

    /* @internal */
    export function tokenIsIdentifierOrKeywordOrGreaterThan(token: 语法): boolean {
        return tokenIsIdentifierOrKeyword(token) || token === 语法.greater;
    }

    export interface Scanner {
        getStartPos(): number;
        getToken(): 语法;
        getTextPos(): number;
        getTokenPos(): number;
        getTokenText(): string;
        getTokenValue(): string;
        hasUnicodeEscape(): boolean;
        hasExtendedUnicodeEscape(): boolean;
        hasPrecedingLineBreak(): boolean;
        isIdentifier(): boolean;
        isUnterminated(): boolean;
        /* @internal */
        getNumericLiteralFlags(): TokenFlags;
        /* @internal */
        getTokenFlags(): TokenFlags;
        reScanInvalidIdentifier(): 语法;
        scan(): 语法;
        getText(): string;
        // Sets the text for the scanner to scan.  An optional subrange starting point and length
        // can be provided to have the scanner only scan a portion of the text.
        setText(text: string | undefined, start?: number, length?: number): void;
        setOnError(onError: ErrorCallback | undefined): void;
        setTextPos(textPos: number): void;
        // Invokes the provided callback then unconditionally restores the scanner to the state it
        // was in immediately prior to invoking the callback.  The result of invoking the callback
        // is returned from this function.
        lookAhead<T>(callback: () => T): T;

        // Invokes the callback with the scanner set to scan the specified range. When the callback
        // returns, the scanner is restored to the state it was in before scanRange was called.
        scanRange<T>(start: number, length: number, callback: () => T): T;

        // Invokes the provided callback.  If the callback returns something falsy, then it restores
        // the scanner to the state it was in immediately prior to invoking the callback.  If the
        // callback returns something truthy, then the scanner state is not rolled back.  The result
        // of invoking the callback is returned from this function.
        tryScan<T>(callback: () => T): T;
    }

    function makeReverseMap(source: ESMap<string, number>): string[] {
        const result: string[] = [];
        source.forEach((value, name) => {
            result[value] = name;
        });
        return result;
    }

    const tokenStrings = makeReverseMap(textToKeyword);

    const textToToken = new Map(getEntries({
        ...关键字对象,
        "-": 语法.minus,
        "+": 语法.plus,
        "[": 语法.l_square,
        "]": 语法.r_square,
        "{": 语法.l_brace,
        "}": 语法.r_brace,
        "(": 语法.l_paren,
        ")": 语法.r_paren,
        "<": 语法.less,
        ">": 语法.greater,
        ":": 语法.colon,
        ";": 语法.semi,
        ",": 语法.comma,
        ".": 语法.dot,
        "=": 语法.equal,
        "?": 语法.question,
        "#": 语法.paste,
        "...": 语法.dotdotdot,
    }));

    export function tokenToString(t: 语法): string | undefined {
        return tokenStrings[t];
    }

    /* @internal */
    export function stringToToken(s: string): 语法 | undefined {
        return textToToken.get(s);
    }

    /* @internal */
    export function computeLineStarts(text: string): number[] {
        const result: number[] = new Array();
        let pos = 0;
        let lineStart = 0;
        while (pos < text.length) {
            const ch = text.charCodeAt(pos);
            pos++;
            switch (ch) {
                case 字符.carriageReturn:
                    if (text.charCodeAt(pos) === 字符.lineFeed) {
                        pos++;
                    }
                // falls through
                case 字符.lineFeed:
                    result.push(lineStart);
                    lineStart = pos;
                    break;
                default:
                    if (ch > 字符.maxAsciiCharacter && isLineBreak(ch)) {
                        result.push(lineStart);
                        lineStart = pos;
                    }
                    break;
            }
        }
        result.push(lineStart);
        return result;
    }

    export function getPositionOfLineAndCharacter(sourceFile: SourceFile, line: number, character: number): number;
    /* @internal */
    export function getPositionOfLineAndCharacter(sourceFile: SourceFile, line: number, character: number, allowEdits?: true): number; // eslint-disable-line @typescript-eslint/unified-signatures
    export function getPositionOfLineAndCharacter(sourceFile: SourceFile, line: number, character: number, allowEdits?: true): number {
        return sourceFile.getPositionOfLineAndCharacter ?
            sourceFile.getPositionOfLineAndCharacter(line, character, allowEdits) :
            computePositionOfLineAndCharacter(getLineStarts(sourceFile), line, character, sourceFile.text, allowEdits);
    }

    /* @internal */
    export function computePositionOfLineAndCharacter(lineStarts: readonly number[], line: number, character: number, debugText?: string, allowEdits?: true): number {
        if (line < 0 || line >= lineStarts.length) {
            if (allowEdits) {
                // Clamp line to nearest allowable value
                line = line < 0 ? 0 : line >= lineStarts.length ? lineStarts.length - 1 : line;
            }
            else {
                Debug.fail(`Bad line number. Line: ${line}, lineStarts.length: ${lineStarts.length} , line map is correct? ${debugText !== undefined ? arraysEqual(lineStarts, computeLineStarts(debugText)) : "unknown"}`);
            }
        }

        const res = lineStarts[line] + character;
        if (allowEdits) {
            // Clamp to nearest allowable values to allow the underlying to be edited without crashing (accuracy is lost, instead)
            // TODO: Somehow track edits between file as it was during the creation of sourcemap we have and the current file and
            // apply them to the computed position to improve accuracy
            return res > lineStarts[line + 1] ? lineStarts[line + 1] : typeof debugText === "string" && res > debugText.length ? debugText.length : res;
        }
        if (line < lineStarts.length - 1) {
            Debug.assert(res < lineStarts[line + 1]);
        }
        else if (debugText !== undefined) {
            Debug.assert(res <= debugText.length); // Allow single character overflow for trailing newline
        }
        return res;
    }

    /* @internal */
    export function getLineStarts(sourceFile: SourceFile): readonly number[] {
        return sourceFile.lineMap || (sourceFile.lineMap = computeLineStarts(sourceFile.text));
    }

    /* @internal */
    export function computeLineAndCharacterOfPosition(lineStarts: readonly number[], position: number): LineAndCharacter {
        const lineNumber = computeLineOfPosition(lineStarts, position);
        return {
            line: lineNumber,
            character: position - lineStarts[lineNumber]
        };
    }

    /**
     * @internal
     * We assume the first line starts at position 0 and 'position' is non-negative.
     */
    export function computeLineOfPosition(lineStarts: readonly number[], position: number, lowerBound?: number) {
        let lineNumber = binarySearch(lineStarts, position);
        if (lineNumber < 0) {
            // If the actual position was not found,
            // the binary search returns the 2's-complement of the next line start
            // e.g. if the line starts at [5, 10, 23, 80] and the position requested was 20
            // then the search will return -2.
            //
            // We want the index of the previous line start, so we subtract 1.
            // Review 2's-complement if this is confusing.
            lineNumber = ~lineNumber - 1;
            Debug.assert(lineNumber !== -1, "position cannot precede the beginning of the file");
        }
        return lineNumber;
    }

    /** @internal */
    export function getLinesBetweenPositions(sourceFile: SourceFile, pos1: number, pos2: number) {
        if (pos1 === pos2) return 0;
        const lineStarts = getLineStarts(sourceFile);
        const lower = Math.min(pos1, pos2);
        const isNegative = lower === pos2;
        const upper = isNegative ? pos1 : pos2;
        const lowerLine = computeLineOfPosition(lineStarts, lower);
        const upperLine = computeLineOfPosition(lineStarts, upper, lowerLine);
        return isNegative ? lowerLine - upperLine : upperLine - lowerLine;
    }

    export function getLineAndCharacterOfPosition(sourceFile: SourceFile, position: number): LineAndCharacter {
        return computeLineAndCharacterOfPosition(getLineStarts(sourceFile), position);
    }

    export function isWhiteSpaceLike(ch: number): boolean {
        return isWhiteSpaceSingleLine(ch) || isLineBreak(ch);
    }

    /** Does not include line breaks. For that, see isWhiteSpaceLike. */
    export function isWhiteSpaceSingleLine(ch: number): boolean {
        // Note: nextLine is in the Zs space, and should be considered to be a whitespace.
        // It is explicitly not a line-break as it isn't in the exact set specified by EcmaScript.
        return ch === 字符.space ||
            ch === 字符.tab ||
            ch === 字符.verticalTab ||
            ch === 字符.formFeed ||
            ch === 字符.nonBreakingSpace ||
            ch === 字符.nextLine ||
            ch === 字符.ogham ||
            ch >= 字符.enQuad && ch <= 字符.zeroWidthSpace ||
            ch === 字符.narrowNoBreakSpace ||
            ch === 字符.mathematicalSpace ||
            ch === 字符.ideographicSpace ||
            ch === 字符.byteOrderMark;
    }

    export function isLineBreak(ch: number): boolean {
        // ES5 7.3:
        // The ECMAScript line terminator characters are listed in Table 3.
        //     Table 3: Line Terminator Characters
        //     Code Unit Value     Name                    Formal Name
        //     \u000A              Line Feed               <LF>
        //     \u000D              Carriage Return         <CR>
        //     \u2028              Line separator          <LS>
        //     \u2029              Paragraph separator     <PS>
        // Only the characters in Table 3 are treated as line terminators. Other new line or line
        // breaking characters are treated as white space but not as line terminators.

        return ch === 字符.lineFeed ||
            ch === 字符.carriageReturn ||
            ch === 字符.lineSeparator ||
            ch === 字符.paragraphSeparator;
    }

    export function 部分标点符号(ch: number): boolean {
        return ch === 字符.openBrace ||
            ch === 字符.openBracket ||
            ch === 字符.openParen ||
            ch === 字符.lessThan ||
            ch === 字符.greaterThan ||
            ch === 字符.doubleQuote ||
            ch === 字符.closeBrace ||
            ch === 字符.closeBracket ||
            ch === 字符.closeParen ||
            ch === 字符.semicolon ||
            ch === 字符.comma ||
            ch === 字符.colon ||
            ch === 字符.equals ||
            ch === 字符.asterisk || 
            ch === 字符.hash
    }

    function isDigit(ch: number): boolean {
        return ch >= 字符._0 && ch <= 字符._9;
    }

    function isHexDigit(ch: number): boolean {
        return isDigit(ch) || ch >= 字符.A && ch <= 字符.F || ch >= 字符.a && ch <= 字符.f;
    }

    function isCodePoint(code: number): boolean {
        return code <= 0x10FFFF;
    }

    /* @internal */
    export function isOctalDigit(ch: number): boolean {
        return ch >= 字符._0 && ch <= 字符._7;
    }

    export function couldStartTrivia(text: string, pos: number): boolean {
        // Keep in sync with skipTrivia
        const ch = text.charCodeAt(pos);
        switch (ch) {
            case 字符.carriageReturn:
            case 字符.lineFeed:
            case 字符.tab:
            case 字符.verticalTab:
            case 字符.formFeed:
            case 字符.space:
            case 字符.slash:
            // starts of normal trivia
            // falls through
            case 字符.lessThan:
            case 字符.bar:
            case 字符.equals:
            case 字符.greaterThan:
                // Starts of conflict marker trivia
                return true;
            case 字符.hash:
                // Only if its the beginning can we have #! trivia
                return pos === 0;
            default:
                return ch > 字符.maxAsciiCharacter;
        }
    }

    /* @internal */
    export function skipTrivia(text: string, pos: number, stopAfterLineBreak?: boolean, stopAtComments?: boolean, inJSDoc?: boolean): number {
        if (positionIsSynthesized(pos)) {
            return pos;
        }

        let canConsumeStar = false;
        // Keep in sync with couldStartTrivia
        while (true) {
            const ch = text.charCodeAt(pos);
            switch (ch) {
                case 字符.carriageReturn:
                    if (text.charCodeAt(pos + 1) === 字符.lineFeed) {
                        pos++;
                    }
                // falls through
                case 字符.lineFeed:
                    pos++;
                    if (stopAfterLineBreak) {
                        return pos;
                    }
                    canConsumeStar = !!inJSDoc;
                    continue;
                case 字符.tab:
                case 字符.verticalTab:
                case 字符.formFeed:
                case 字符.space:
                    pos++;
                    continue;
                case 字符.slash:
                    if (stopAtComments) {
                        break;
                    }
                    if (text.charCodeAt(pos + 1) === 字符.slash) {
                        pos += 2;
                        while (pos < text.length) {
                            if (isLineBreak(text.charCodeAt(pos))) {
                                break;
                            }
                            pos++;
                        }
                        canConsumeStar = false;
                        continue;
                    }
                    if (text.charCodeAt(pos + 1) === 字符.asterisk) {
                        pos += 2;
                        while (pos < text.length) {
                            if (text.charCodeAt(pos) === 字符.asterisk && text.charCodeAt(pos + 1) === 字符.slash) {
                                pos += 2;
                                break;
                            }
                            pos++;
                        }
                        canConsumeStar = false;
                        continue;
                    }
                    break;

                case 字符.lessThan:
                case 字符.bar:
                case 字符.equals:
                case 字符.greaterThan:
                    if (isConflictMarkerTrivia(text, pos)) {
                        pos = scanConflictMarkerTrivia(text, pos);
                        canConsumeStar = false;
                        continue;
                    }
                    break;

                case 字符.hash:
                    if (pos === 0 && isShebangTrivia(text, pos)) {
                        pos = scanShebangTrivia(text, pos);
                        canConsumeStar = false;
                        continue;
                    }
                    break;

                case 字符.asterisk:
                    if (canConsumeStar) {
                        pos++;
                        canConsumeStar = false;
                        continue;
                    }
                    break;

                default:
                    if (ch > 字符.maxAsciiCharacter && (isWhiteSpaceLike(ch))) {
                        pos++;
                        continue;
                    }
                    break;
            }
            return pos;
        }
    }
    
    const shebangTriviaRegex = /^#!.*/;
    
    /*@internal*/
    export function isShebangTrivia(text: string, pos: number) {
        // Shebangs check must only be done at the start of the file
        Debug.assert(pos === 0);
        return shebangTriviaRegex.test(text);
    }

    /*@internal*/
    export function scanShebangTrivia(text: string, pos: number) {
        const shebang = shebangTriviaRegex.exec(text)![0];
        pos = pos + shebang.length;
        return pos;
    }

    
    export function isIdentifierStart(ch: number): boolean {
        return isUnicodeIdentifierPart(ch);
    }

    export function isIdentifierPart(ch: number): boolean {
        return isUnicodeIdentifierPart(ch);
    }

    /* @internal */
    export function isIdentifierText(name: string): boolean {
        let ch = codePointAt(name, 0);
        if (!isIdentifierStart(ch)) {
            return false;
        }

        for (let i = charSize(ch); i < name.length; i += charSize(ch)) {
            if (!isIdentifierPart(ch = codePointAt(name, i))) {
                return false;
            }
        }

        return true;
    }

    // All conflict markers consist of the same character repeated seven times.  If it is
    // a <<<<<<< or >>>>>>> marker then it is also followed by a space.
    const mergeConflictMarkerLength = "<<<<<<<".length;

    function isConflictMarkerTrivia(text: string, pos: number) {
        Debug.assert(pos >= 0);

        // Conflict markers must be at the start of a line.
        if (pos === 0 || isLineBreak(text.charCodeAt(pos - 1))) {
            const ch = text.charCodeAt(pos);

            if ((pos + mergeConflictMarkerLength) < text.length) {
                for (let i = 0; i < mergeConflictMarkerLength; i++) {
                    if (text.charCodeAt(pos + i) !== ch) {
                        return false;
                    }
                }

                return ch === 字符.equals ||
                    text.charCodeAt(pos + mergeConflictMarkerLength) === 字符.space;
            }
        }

        return false;
    }

    function scanConflictMarkerTrivia(text: string, pos: number, error?: (diag: DiagnosticMessage, pos?: number, len?: number) => void) {
        if (error) {
            error(Diagnostics.Merge_conflict_marker_encountered, pos, mergeConflictMarkerLength);
        }

        const ch = text.charCodeAt(pos);
        const len = text.length;

        if (ch === 字符.lessThan || ch === 字符.greaterThan) {
            while (pos < len && !isLineBreak(text.charCodeAt(pos))) {
                pos++;
            }
        }
        else {
            Debug.assert(ch === 字符.bar || ch === 字符.equals);
            // Consume everything from the start of a ||||||| or ======= marker to the start
            // of the next ======= or >>>>>>> marker.
            while (pos < len) {
                const currentChar = text.charCodeAt(pos);
                if ((currentChar === 字符.equals || currentChar === 字符.greaterThan) && currentChar !== ch && isConflictMarkerTrivia(text, pos)) {
                    break;
                }

                pos++;
            }
        }

        return pos;
    }

    // Creates a scanner over a (possibly unspecified) range of a piece of text.
    export function createScanner(textInitial?: string,
        onError?: ErrorCallback,
        start?: number,
        length?: number): Scanner {

        let text = textInitial!;

        // Current position (end position of text of current token)
        let pos: number;


        // end of text
        let end: number;

        // Start position of whitespace before current token
        let startPos: number;

        // Start position of text of current token
        let tokenPos: number;

        let token: 语法;
        let tokenValue!: string;
        let tokenFlags: TokenFlags;

        setText(text, start, length);

        const scanner: Scanner = {
            getStartPos: () => startPos,
            getTextPos: () => pos,
            getToken: () => token,
            getTokenPos: () => tokenPos,
            getTokenText: () => text.substring(tokenPos, pos),
            getTokenValue: () => tokenValue,
            hasUnicodeEscape: () => (tokenFlags & TokenFlags.UnicodeEscape) !== 0,
            hasExtendedUnicodeEscape: () => (tokenFlags & TokenFlags.ExtendedUnicodeEscape) !== 0,
            hasPrecedingLineBreak: () => (tokenFlags & TokenFlags.PrecedingLineBreak) !== 0,
            isIdentifier: () => token === 语法.id || token > 语法.FalseVal,
            isUnterminated: () => (tokenFlags & TokenFlags.Unterminated) !== 0,
            getNumericLiteralFlags: () => tokenFlags & TokenFlags.NumericLiteralFlags,
            getTokenFlags: () => tokenFlags,
            reScanInvalidIdentifier,
            scan,
            getText,
            setText,
            setOnError,
            setTextPos,
            tryScan,
            lookAhead,
            scanRange,
        };

        if (Debug.isDebugging) {
            Object.defineProperty(scanner, "__debugShowCurrentPositionInText", {
                get: () => {
                    const text = scanner.getText();
                    return text.slice(0, scanner.getStartPos()) + "║" + text.slice(scanner.getStartPos());
                },
            });
        }

        return scanner;

        function error(message: DiagnosticMessage): void;
        function error(message: DiagnosticMessage, errPos: number, length: number): void;
        function error(message: DiagnosticMessage, errPos: number = pos, length?: number): void {
            if (onError) {
                const oldPos = pos;
                pos = errPos;
                onError(message, length || 0);
                pos = oldPos;
            }
        }

        function scanNumberFragment(): string {
            let start = pos;
            let allowSeparator = false;
            let isPreviousTokenSeparator = false;
            let result = "";
            while (true) {
                const ch = text.charCodeAt(pos);
                if (ch === 字符._) {
                    tokenFlags |= TokenFlags.ContainsSeparator;
                    if (allowSeparator) {
                        allowSeparator = false;
                        isPreviousTokenSeparator = true;
                        result += text.substring(start, pos);
                    }
                    else if (isPreviousTokenSeparator) {
                        error(Diagnostics.Multiple_consecutive_numeric_separators_are_not_permitted, pos, 1);
                    }
                    else {
                        error(Diagnostics.Numeric_separators_are_not_allowed_here, pos, 1);
                    }
                    pos++;
                    start = pos;
                    continue;
                }
                if (isDigit(ch)) {
                    allowSeparator = true;
                    isPreviousTokenSeparator = false;
                    pos++;
                    continue;
                }
                break;
            }
            if (text.charCodeAt(pos - 1) === 字符._) {
                error(Diagnostics.Numeric_separators_are_not_allowed_here, pos - 1, 1);
            }
            return result + text.substring(start, pos);
        }

        function scanNumber(): { type: 语法, value: string } {
            const start = pos;
            const mainFragment = scanNumberFragment();
            let decimalFragment: string | undefined;
            let scientificFragment: string | undefined;
            if (text.charCodeAt(pos) === 字符.dot) {
                pos++;
                decimalFragment = scanNumberFragment();
            }
            let end = pos;
            if (text.charCodeAt(pos) === 字符.E || text.charCodeAt(pos) === 字符.e) {
                pos++;
                tokenFlags |= TokenFlags.Scientific;
                if (text.charCodeAt(pos) === 字符.plus || text.charCodeAt(pos) === 字符.minus) pos++;
                const preNumericPart = pos;
                const finalFragment = scanNumberFragment();
                if (!finalFragment) {
                    error(Diagnostics.Digit_expected);
                }
                else {
                    scientificFragment = text.substring(end, preNumericPart) + finalFragment;
                    end = pos;
                }
            }
            let result: string;
            if (tokenFlags & TokenFlags.ContainsSeparator) {
                result = mainFragment;
                if (decimalFragment) {
                    result += "." + decimalFragment;
                }
                if (scientificFragment) {
                    result += scientificFragment;
                }
            }
            else {
                result = text.substring(start, end); // No need to use all the fragments; no _ removal needed
            }

            if (decimalFragment !== undefined || tokenFlags & TokenFlags.Scientific) {
                return {
                    type: 语法.数字字面量,
                    value: "" + +result // if value is not an integer, it can be safely coerced to a number
                };
            }
            else {
                tokenValue = result;
                const type = checkBigIntSuffix(); // if value is an integer, check whether it is a bigint
                return { type, value: tokenValue };
            }
        }


        function scanOctalDigits(): number {
            const start = pos;
            while (isOctalDigit(text.charCodeAt(pos))) {
                pos++;
            }
            return +(text.substring(start, pos));
        }

        /**
         * Scans the given number of hexadecimal digits in the text,
         * returning -1 if the given number is unavailable.
         */
        function scanExactNumberOfHexDigits(count: number, canHaveSeparators: boolean): number {
            const valueString = scanHexDigits(/*minCount*/ count, /*scanAsManyAsPossible*/ false, canHaveSeparators);
            return valueString ? parseInt(valueString, 16) : -1;
        }

        /**
         * Scans as many hexadecimal digits as are available in the text,
         * returning "" if the given number of digits was unavailable.
         */
        function scanMinimumNumberOfHexDigits(count: number, canHaveSeparators: boolean): string {
            return scanHexDigits(/*minCount*/ count, /*scanAsManyAsPossible*/ true, canHaveSeparators);
        }

        function scanHexDigits(minCount: number, scanAsManyAsPossible: boolean, canHaveSeparators: boolean): string {
            let valueChars: number[] = [];
            let allowSeparator = false;
            let isPreviousTokenSeparator = false;
            while (valueChars.length < minCount || scanAsManyAsPossible) {
                let ch = text.charCodeAt(pos);
                if (canHaveSeparators && ch === 字符._) {
                    tokenFlags |= TokenFlags.ContainsSeparator;
                    if (allowSeparator) {
                        allowSeparator = false;
                        isPreviousTokenSeparator = true;
                    }
                    else if (isPreviousTokenSeparator) {
                        error(Diagnostics.Multiple_consecutive_numeric_separators_are_not_permitted, pos, 1);
                    }
                    else {
                        error(Diagnostics.Numeric_separators_are_not_allowed_here, pos, 1);
                    }
                    pos++;
                    continue;
                }
                allowSeparator = canHaveSeparators;
                if (ch >= 字符.A && ch <= 字符.F) {
                    ch += 字符.a - 字符.A; // standardize hex literals to lowercase
                }
                else if (!((ch >= 字符._0 && ch <= 字符._9) ||
                    (ch >= 字符.a && ch <= 字符.f)
                )) {
                    break;
                }
                valueChars.push(ch);
                pos++;
                isPreviousTokenSeparator = false;
            }
            if (valueChars.length < minCount) {
                valueChars = [];
            }
            if (text.charCodeAt(pos - 1) === 字符._) {
                error(Diagnostics.Numeric_separators_are_not_allowed_here, pos - 1, 1);
            }
            return String.fromCharCode(...valueChars);
        }

        function scanString(): string {
            const quote = text.charCodeAt(pos);
            pos++;
            let result = "";
            let start = pos;
            while (true) {
                if (pos >= end) {
                    result += text.substring(start, pos);
                    tokenFlags |= TokenFlags.Unterminated;
                    error(Diagnostics.Unterminated_string_literal);
                    break;
                }
                const ch = text.charCodeAt(pos);
                if (ch === quote) {
                    result += text.substring(start, pos);
                    pos++;
                    break;
                }
                if (ch === 字符.backslash) {
                    result += text.substring(start, pos);
                    result += scanEscapeSequence();
                    start = pos;
                    continue;
                }
                if (isLineBreak(ch)) {
                    result += text.substring(start, pos);
                    tokenFlags |= TokenFlags.Unterminated;
                    error(Diagnostics.Unterminated_string_literal);
                    break;
                }
                pos++;
            }
            return result;
        }

        function scanCodeFragment(): string {
            pos += 2;
            let result = "";
            let start = pos;
            while (true) {
                if (pos >= end) {
                    result += text.substring(start, pos);
                    tokenFlags |= TokenFlags.Unterminated;
                    error(Diagnostics.Unterminated_string_literal);
                    break;
                }
                const ch = text.charCodeAt(pos);
                if (ch === 字符.closeBrace && text.charCodeAt(pos + 1) === 字符.closeBracket) {
                    result += text.substring(start, pos);
                    pos += 2;
                    break;
                }
                if (ch === 字符.backslash) {
                    result += text.substring(start, pos);
                    result += scanEscapeSequence();
                    start = pos;
                    continue;
                }

                // Speculated ECMAScript 6 Spec 11.8.6.1:
                // <CR><LF> and <CR> LineTerminatorSequences are normalized to <LF> for Template Values
                if (ch === 字符.carriageReturn) {
                    result += text.substring(start, pos);
                    pos++;

                    if (pos < end && text.charCodeAt(pos) === 字符.lineFeed) {
                        pos++;
                    }

                    result += "\n";
                    start = pos;
                    continue;
                }
                pos++;
            }
            return result;
        }
        function scanEscapeSequence(isTaggedTemplate?: boolean): string {
            const start = pos;
            pos++;
            if (pos >= end) {
                error(Diagnostics.Unexpected_end_of_text);
                return "";
            }
            const ch = text.charCodeAt(pos);
            pos++;
            switch (ch) {
                case 字符._0:
                    // '\01'
                    if (isTaggedTemplate && pos < end && isDigit(text.charCodeAt(pos))) {
                        pos++;
                        tokenFlags |= TokenFlags.ContainsInvalidEscape;
                        return text.substring(start, pos);
                    }
                    return "\0";
                case 字符.b:
                    return "\b";
                case 字符.t:
                    return "\t";
                case 字符.n:
                    return "\n";
                case 字符.v:
                    return "\v";
                case 字符.f:
                    return "\f";
                case 字符.r:
                    return "\r";
                case 字符.singleQuote:
                    return "\'";
                case 字符.doubleQuote:
                    return "\"";
                case 字符.u:
                    if (isTaggedTemplate) {
                        // '\u' or '\u0' or '\u00' or '\u000'
                        for (let escapePos = pos; escapePos < pos + 4; escapePos++) {
                            if (escapePos < end && !isHexDigit(text.charCodeAt(escapePos)) && text.charCodeAt(escapePos) !== 字符.openBrace) {
                                pos = escapePos;
                                tokenFlags |= TokenFlags.ContainsInvalidEscape;
                                return text.substring(start, pos);
                            }
                        }
                    }
                    // '\u{DDDDDDDD}'
                    if (pos < end && text.charCodeAt(pos) === 字符.openBrace) {
                        pos++;

                        // '\u{'
                        if (isTaggedTemplate && !isHexDigit(text.charCodeAt(pos))) {
                            tokenFlags |= TokenFlags.ContainsInvalidEscape;
                            return text.substring(start, pos);
                        }

                        if (isTaggedTemplate) {
                            const savePos = pos;
                            const escapedValueString = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ false);
                            const escapedValue = escapedValueString ? parseInt(escapedValueString, 16) : -1;

                            // '\u{Not Code Point' or '\u{CodePoint'
                            if (!isCodePoint(escapedValue) || text.charCodeAt(pos) !== 字符.closeBrace) {
                                tokenFlags |= TokenFlags.ContainsInvalidEscape;
                                return text.substring(start, pos);
                            }
                            else {
                                pos = savePos;
                            }
                        }
                        tokenFlags |= TokenFlags.ExtendedUnicodeEscape;
                        return scanExtendedUnicodeEscape();
                    }

                    tokenFlags |= TokenFlags.UnicodeEscape;
                    // '\uDDDD'
                    return scanHexadecimalEscape(/*numDigits*/ 4);

                case 字符.x:
                    if (isTaggedTemplate) {
                        if (!isHexDigit(text.charCodeAt(pos))) {
                            tokenFlags |= TokenFlags.ContainsInvalidEscape;
                            return text.substring(start, pos);
                        }
                        else if (!isHexDigit(text.charCodeAt(pos + 1))) {
                            pos++;
                            tokenFlags |= TokenFlags.ContainsInvalidEscape;
                            return text.substring(start, pos);
                        }
                    }
                    // '\xDD'
                    return scanHexadecimalEscape(/*numDigits*/ 2);

                // when encountering a LineContinuation (i.e. a backslash and a line terminator sequence),
                // the line terminator is interpreted to be "the empty code unit sequence".
                case 字符.carriageReturn:
                    if (pos < end && text.charCodeAt(pos) === 字符.lineFeed) {
                        pos++;
                    }
                // falls through
                case 字符.lineFeed:
                case 字符.lineSeparator:
                case 字符.paragraphSeparator:
                    return "";
                default:
                    return String.fromCharCode(ch);
            }
        }

        function scanHexadecimalEscape(numDigits: number): string {
            const escapedValue = scanExactNumberOfHexDigits(numDigits, /*canHaveSeparators*/ false);

            if (escapedValue >= 0) {
                return String.fromCharCode(escapedValue);
            }
            else {
                error(Diagnostics.Hexadecimal_digit_expected);
                return "";
            }
        }

        function scanExtendedUnicodeEscape(): string {
            const escapedValueString = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ false);
            const escapedValue = escapedValueString ? parseInt(escapedValueString, 16) : -1;
            let isInvalidExtendedEscape = false;

            // Validate the value of the digit
            if (escapedValue < 0) {
                error(Diagnostics.Hexadecimal_digit_expected);
                isInvalidExtendedEscape = true;
            }
            else if (escapedValue > 0x10FFFF) {
                error(Diagnostics.An_extended_Unicode_escape_value_must_be_between_0x0_and_0x10FFFF_inclusive);
                isInvalidExtendedEscape = true;
            }

            if (pos >= end) {
                error(Diagnostics.Unexpected_end_of_text);
                isInvalidExtendedEscape = true;
            }
            else if (text.charCodeAt(pos) === 字符.closeBrace) {
                // Only swallow the following character up if it's a '}'.
                pos++;
            }
            else {
                error(Diagnostics.Unterminated_Unicode_escape_sequence);
                isInvalidExtendedEscape = true;
            }

            if (isInvalidExtendedEscape) {
                return "";
            }

            return utf16EncodeAsString(escapedValue);
        }

        // Current character is known to be a backslash. Check for Unicode escape of the form '\uXXXX'
        // and return code point value if valid Unicode escape is found. Otherwise return -1.
        function peekUnicodeEscape(): number {
            if (pos + 5 < end && text.charCodeAt(pos + 1) === 字符.u) {
                const start = pos;
                pos += 2;
                const value = scanExactNumberOfHexDigits(4, /*canHaveSeparators*/ false);
                pos = start;
                return value;
            }
            return -1;
        }


        function peekExtendedUnicodeEscape(): number {
            const start = pos;
            pos += 3;
            const escapedValueString = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ false);
            const escapedValue = escapedValueString ? parseInt(escapedValueString, 16) : -1;
            pos = start;
            return escapedValue;
        }

        function scanIdentifierParts(): string {
            let result = "";
            let start = pos;
            while (pos < end) {
                let ch = codePointAt(text, pos);
                if (isIdentifierPart(ch)) {
                    pos += charSize(ch);
                }
                else if (ch === 字符.backslash) {
                    ch = peekExtendedUnicodeEscape();
                    if (ch >= 0 && isIdentifierPart(ch)) {
                        pos += 3;
                        tokenFlags |= TokenFlags.ExtendedUnicodeEscape;
                        result += scanExtendedUnicodeEscape();
                        start = pos;
                        continue;
                    }
                    ch = peekUnicodeEscape();
                    if (!(ch >= 0 && isIdentifierPart(ch))) {
                        break;
                    }
                    tokenFlags |= TokenFlags.UnicodeEscape;
                    result += text.substring(start, pos);
                    result += utf16EncodeAsString(ch);
                    // Valid Unicode escape is always six characters
                    pos += 6;
                    start = pos;
                }
                else {
                    break;
                }
            }
            result += text.substring(start, pos);
            return result;
        }

        function 获取取反运算符关键字() {
            const keyword = 文本转取反关键字.get(tokenValue);
            if (keyword !== undefined) {
                return token = keyword;
            }
            Debug.fail();
        }

        function 获取预处理关键字() {
            // 可以先过滤 开始字符及长度
            const keyword = 预处理文本关键字.get(tokenValue);
            if (keyword !== undefined) {
                return token = keyword;
            }
            Debug.fail();
        }

        function getIdentifierToken(): 语法.id | 关键字 {
            // 可以先过滤 开始字符及长度
            const keyword = textToKeyword.get(tokenValue) || 预处理文本关键字.get(tokenValue);
            if (keyword !== undefined) {
                return token = keyword;
            }
            return token = 语法.id;
        }

        function scanBinaryOrOctalDigits(base: 2 | 8): string {
            let value = "";
            // For counting number of digits; Valid binaryIntegerLiteral must have at least one binary digit following B or b.
            // Similarly valid octalIntegerLiteral must have at least one octal digit following o or O.
            let separatorAllowed = false;
            let isPreviousTokenSeparator = false;
            while (true) {
                const ch = text.charCodeAt(pos);
                // Numeric separators are allowed anywhere within a numeric literal, except not at the beginning, or following another separator
                if (ch === 字符._) {
                    tokenFlags |= TokenFlags.ContainsSeparator;
                    if (separatorAllowed) {
                        separatorAllowed = false;
                        isPreviousTokenSeparator = true;
                    }
                    else if (isPreviousTokenSeparator) {
                        error(Diagnostics.Multiple_consecutive_numeric_separators_are_not_permitted, pos, 1);
                    }
                    else {
                        error(Diagnostics.Numeric_separators_are_not_allowed_here, pos, 1);
                    }
                    pos++;
                    continue;
                }
                separatorAllowed = true;
                if (!isDigit(ch) || ch - 字符._0 >= base) {
                    break;
                }
                value += text[pos];
                pos++;
                isPreviousTokenSeparator = false;
            }
            if (text.charCodeAt(pos - 1) === 字符._) {
                // Literal ends with underscore - not allowed
                error(Diagnostics.Numeric_separators_are_not_allowed_here, pos - 1, 1);
            }
            return value;
        }

        function checkBigIntSuffix(): 语法 {
            const numericValue = tokenFlags & TokenFlags.BinarySpecifier
                ? parseInt(tokenValue.slice(2), 2) // skip "0b"
                : tokenFlags & TokenFlags.OctalSpecifier
                    ? parseInt(tokenValue.slice(2), 8) // skip "0o"
                    : +tokenValue;
            tokenValue = "" + numericValue;
            return 语法.数字字面量;
        }

        function scan(): 语法 {
            startPos = pos;
            tokenFlags = TokenFlags.None;
            while (true) {
                tokenPos = pos;
                if (pos >= end) {
                    return token = 语法.Eof;
                }
                const ch = codePointAt(text, pos);

                // Special handling for shebang
                if (ch === 字符.hash && pos === 0 && isShebangTrivia(text, pos)) {
                    pos = scanShebangTrivia(text, pos);
                    continue;
                }

                switch (ch) {
                    case 字符.lineFeed:
                    case 字符.carriageReturn:
                        tokenFlags |= TokenFlags.PrecedingLineBreak;
                        pos++;
                        continue;
                    case 字符.tab:
                    case 字符.verticalTab:
                    case 字符.formFeed:
                    case 字符.space:
                    case 字符.nonBreakingSpace:
                    case 字符.ogham:
                    case 字符.enQuad:
                    case 字符.emQuad:
                    case 字符.enSpace:
                    case 字符.emSpace:
                    case 字符.threePerEmSpace:
                    case 字符.fourPerEmSpace:
                    case 字符.sixPerEmSpace:
                    case 字符.figureSpace:
                    case 字符.punctuationSpace:
                    case 字符.thinSpace:
                    case 字符.hairSpace:
                    case 字符.zeroWidthSpace:
                    case 字符.narrowNoBreakSpace:
                    case 字符.mathematicalSpace:
                    case 字符.ideographicSpace:
                    case 字符.byteOrderMark:
                        pos++;
                        continue;
                    case 字符.slash:
                        if (text.charCodeAt(pos + 1) === 字符.slash) {
                            pos += 2;
                            while (pos < end) {
                                if (isLineBreak(text.charCodeAt(pos))) {
                                    break;
                                }
                                pos++;
                            }
                            continue;
                        }
                        if (text.charCodeAt(pos + 1) === 字符.asterisk) {
                            pos += 2;
                            if (text.charCodeAt(pos) === 字符.asterisk && text.charCodeAt(pos + 1) !== 字符.slash) {
                                tokenFlags |= TokenFlags.PrecedingJSDocComment;
                            }

                            let commentClosed = false;
                            while (pos < end) {
                                const ch = text.charCodeAt(pos);

                                if (ch === 字符.asterisk && text.charCodeAt(pos + 1) === 字符.slash) {
                                    pos += 2;
                                    commentClosed = true;
                                    break;
                                }
                                pos++;

                                if (isLineBreak(ch)) {
                                    tokenFlags |= TokenFlags.PrecedingLineBreak;
                                }
                            }
                            if (!commentClosed) {
                                error(Diagnostics.Asterisk_Slash_expected);
                            }
                            continue;
                        }
                        pos++;
                        continue;
                    case 字符.semicolon:
                        pos++
                        return token = 语法.semi;
                    case 字符.exclamation:
                        pos++;
                        const 取反运算符关键字 = 扫描取反运算关键字(ch);
                        Debug.assert(取反运算符关键字);
                        return token = 取反运算符关键字;
                    case 字符.hash:
                        pos++;
                        if ((tokenFlags & TokenFlags.PrecedingLineBreak) !== 0) {
                            const 预处理关键字 = 扫描预处理关键字(ch);
                            Debug.assert(预处理关键字);
                            return token = 预处理关键字;
                        }
                        return token = 语法.paste
                    case 字符.question:
                        pos++;
                        return token = 语法.question;
                    case 字符.doubleQuote:
                        tokenValue = scanString();
                        return token = 语法.文本字面量;
                    case 字符.plus:
                        pos++;
                        return token = 语法.plus;
                    case 字符.comma:
                        pos++;
                        return token = 语法.comma;
                    case 字符.colon:
                        pos++;
                        return token = 语法.colon;
                    case 字符.minus:
                        pos++;
                        return token = 语法.minus;
                    case 字符.dot:
                        if (isDigit(text.charCodeAt(pos + 1))) {
                            tokenValue = scanNumber().value;
                            return token = 语法.数字字面量;
                        }
                        if (text.charCodeAt(pos + 1) === 字符.dot && text.charCodeAt(pos + 2) === 字符.dot) {
                            return pos += 3, token = 语法.dotdotdot;
                        }
                        pos++;
                        continue;
                    case 字符._0:
                        if (pos + 2 < end && (text.charCodeAt(pos + 1) === 字符.X || text.charCodeAt(pos + 1) === 字符.x)) {
                            pos += 2;
                            tokenValue = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ true);
                            if (!tokenValue) {
                                error(Diagnostics.Hexadecimal_digit_expected);
                                tokenValue = "0";
                            }
                            tokenValue = "0x" + tokenValue;
                            tokenFlags |= TokenFlags.HexSpecifier;
                            return token = checkBigIntSuffix();
                        }
                        else if (pos + 2 < end && (text.charCodeAt(pos + 1) === 字符.B || text.charCodeAt(pos + 1) === 字符.b)) {
                            pos += 2;
                            tokenValue = scanBinaryOrOctalDigits(/* base */ 2);
                            if (!tokenValue) {
                                error(Diagnostics.Binary_digit_expected);
                                tokenValue = "0";
                            }
                            tokenValue = "0b" + tokenValue;
                            tokenFlags |= TokenFlags.BinarySpecifier;
                            return token = checkBigIntSuffix();
                        }
                        else if (pos + 2 < end && (text.charCodeAt(pos + 1) === 字符.O || text.charCodeAt(pos + 1) === 字符.o)) {
                            pos += 2;
                            tokenValue = scanBinaryOrOctalDigits(/* base */ 8);
                            if (!tokenValue) {
                                error(Diagnostics.Octal_digit_expected);
                                tokenValue = "0";
                            }
                            tokenValue = "0o" + tokenValue;
                            tokenFlags |= TokenFlags.OctalSpecifier;
                            return token = checkBigIntSuffix();
                        }
                        // Try to parse as an octal
                        if (pos + 1 < end && isOctalDigit(text.charCodeAt(pos + 1))) {
                            tokenValue = "" + scanOctalDigits();
                            tokenFlags |= TokenFlags.Octal;
                            return token = 语法.数字字面量;
                        }
                    case 字符._1:
                    case 字符._2:
                    case 字符._3:
                    case 字符._4:
                    case 字符._5:
                    case 字符._6:
                    case 字符._7:
                    case 字符._8:
                    case 字符._9:
                        ({ type: token, value: tokenValue } = scanNumber());
                        return token;
                    case 字符.lessThan:
                        pos++;
                        return token = 语法.less;
                    case 字符.equals:
                        pos++;
                        return token = 语法.equal;
                    case 字符.greaterThan:
                        pos++;
                        return token = 语法.greater;
                    case 字符.openBrace:
                        pos++;
                        return token = 语法.l_brace;
                    case 字符.closeBrace:
                        pos++;
                        return token = 语法.r_brace;
                    case 字符.openBracket:
                        if (text.charCodeAt(pos + 1) === 字符.openBrace) {
                            tokenValue = scanCodeFragment();
                            return token = 语法.CodeFragment;
                        }
                        pos++;
                        return token = 语法.l_square;
                    case 字符.closeBracket:
                        pos++;
                        return token = 语法.r_square;
                    case 字符.openParen:
                        pos++;
                        return token = 语法.l_paren;
                    case 字符.closeParen:
                        pos++;
                        return token = 语法.r_paren;
                    case 字符.$:
                        pos++;
                        return token = 语法.美元;

                    default:
                        const identifierKind = scanIdentifier(ch);
                        if (identifierKind) {
                            return token = identifierKind;
                        }
                        else if (isWhiteSpaceSingleLine(ch)) {
                            pos += charSize(ch);
                            continue;
                        }
                        else if (isLineBreak(ch)) {
                            tokenFlags |= TokenFlags.PrecedingLineBreak;
                            pos += charSize(ch);
                            continue;
                        }
                        error(Diagnostics.Invalid_character);
                        pos += charSize(ch);
                        return token = 语法.Error;
                }
            }
        }

        function reScanInvalidIdentifier(): 语法 {
            Debug.assert(token === 语法.Error, "'reScanInvalidIdentifier' should only be called when the current token is 'SyntaxKind.Unknown'.");
            pos = tokenPos = startPos;
            tokenFlags = 0;
            const ch = codePointAt(text, pos);
            const identifierKind = scanIdentifier(ch);
            if (identifierKind) {
                return token = identifierKind;
            }
            pos += charSize(ch);
            return token; // Still `SyntaKind.Unknown`
        }

        function 扫描取反运算关键字(startCharacter: number) {
            let ch = startCharacter;
            if (isIdentifierStart(ch)) {
                pos += charSize(ch);
                while (pos < end && isIdentifierPart(ch = codePointAt(text, pos))) pos += charSize(ch);
                tokenValue = text.substring(tokenPos, pos);
                if (ch === 字符.backslash) {
                    tokenValue += scanIdentifierParts();
                }
                return 获取取反运算符关键字();
            }
        }

        function 扫描预处理关键字(startCharacter: number) {
            let ch = startCharacter;
            if (isIdentifierStart(ch)) {
                pos += charSize(ch);
                while (pos < end && isIdentifierPart(ch = codePointAt(text, pos))) pos += charSize(ch);
                tokenValue = text.substring(tokenPos, pos);
                if (ch === 字符.backslash) {
                    tokenValue += scanIdentifierParts();
                }
                return 获取预处理关键字();
            }
        }

        function scanIdentifier(startCharacter: number) {
            let ch = startCharacter;
            if (isIdentifierStart(ch)) {
                pos += charSize(ch);
                while (pos < end && isIdentifierPart(ch = codePointAt(text, pos))) pos += charSize(ch);
                tokenValue = text.substring(tokenPos, pos);
                if (ch === 字符.backslash) {
                    tokenValue += scanIdentifierParts();
                }
                return getIdentifierToken();
            }
        }

        function speculationHelper<T>(callback: () => T, isLookahead: boolean): T {
            const savePos = pos;
            const saveStartPos = startPos;
            const saveTokenPos = tokenPos;
            const saveToken = token;
            const saveTokenValue = tokenValue;
            const saveTokenFlags = tokenFlags;
            const result = callback();

            // If our callback returned something 'falsy' or we're just looking ahead,
            // then unconditionally restore us to where we were.
            if (!result || isLookahead) {
                pos = savePos;
                startPos = saveStartPos;
                tokenPos = saveTokenPos;
                token = saveToken;
                tokenValue = saveTokenValue;
                tokenFlags = saveTokenFlags;
            }
            return result;
        }

        function scanRange<T>(start: number, length: number, callback: () => T): T {
            const saveEnd = end;
            const savePos = pos;
            const saveStartPos = startPos;
            const saveTokenPos = tokenPos;
            const saveToken = token;
            const saveTokenValue = tokenValue;
            const saveTokenFlags = tokenFlags;

            setText(text, start, length);
            const result = callback();

            end = saveEnd;
            pos = savePos;
            startPos = saveStartPos;
            tokenPos = saveTokenPos;
            token = saveToken;
            tokenValue = saveTokenValue;
            tokenFlags = saveTokenFlags;
            return result;
        }

        function lookAhead<T>(callback: () => T): T {
            return speculationHelper(callback, /*isLookahead*/ true);
        }

        function tryScan<T>(callback: () => T): T {
            return speculationHelper(callback, /*isLookahead*/ false);
        }

        function getText(): string {
            return text;
        }

        function setText(newText: string | undefined, start: number | undefined, length: number | undefined) {
            text = newText || "";
            end = length === undefined ? text.length : start! + length;
            setTextPos(start || 0);
        }

        function setOnError(errorCallback: ErrorCallback | undefined) {
            onError = errorCallback;
        }

        function setTextPos(textPos: number) {
            Debug.assert(textPos >= 0);
            pos = textPos;
            startPos = textPos;
            tokenPos = textPos;
            token = 语法.Error;
            tokenValue = undefined!;
            tokenFlags = TokenFlags.None;
        }
    }
    
    /* @internal */
    export const codePointAt: (s: string, i: number) => number = (String.prototype as any).codePointAt ? (s, i) => (s as any).codePointAt(i) : function codePointAt(str, i): number {
        // from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt
        const size = str.length;
        // Account for out-of-bounds indices:
        if (i < 0 || i >= size) {
            return undefined!; // String.codePointAt returns `undefined` for OOB indexes
        }
        // Get the first code unit
        const first = str.charCodeAt(i);
        // check if it’s the start of a surrogate pair
        if (first >= 0xD800 && first <= 0xDBFF && size > i + 1) { // high surrogate and there is a next code unit
            const second = str.charCodeAt(i + 1);
            if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate
                // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
                return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
            }
        }
        return first;
    };

    /* @internal */
    export function charSize(ch: number) {
        if (ch >= 0x10000) {
            return 2;
        }
        return 1;
    }

    // Derived from the 10.1.1 UTF16Encoding of the ES6 Spec.
    function utf16EncodeAsStringFallback(codePoint: number) {
        Debug.assert(0x0 <= codePoint && codePoint <= 0x10FFFF);

        if (codePoint <= 65535) {
            return String.fromCharCode(codePoint);
        }

        const codeUnit1 = Math.floor((codePoint - 65536) / 1024) + 0xD800;
        const codeUnit2 = ((codePoint - 65536) % 1024) + 0xDC00;

        return String.fromCharCode(codeUnit1, codeUnit2);
    }

    const utf16EncodeAsStringWorker: (codePoint: number) => string = (String as any).fromCodePoint ? codePoint => (String as any).fromCodePoint(codePoint) : utf16EncodeAsStringFallback;

    /* @internal */
    export function utf16EncodeAsString(codePoint: number) {
        return utf16EncodeAsStringWorker(codePoint);
    }
}